/**
 * @(#)GenericEnumUserType.java Mar 12, 2007
 *
 * Copyright (c) 2005-2010 Psiqueware S.A. de C.V. All Rights Reserved.
 * This software is the proprietary information Psiqueware S.A. de C.V.
 * Use is subject to license terms.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * <br/>Usuario : AngelElectrico
 * <br/>Creaci&oacute;n: Mar 12, 2007 - 11:55:23 AM
 * <br/>
 * <br/>
 * <br/><t5>Cambios :</t5>
 * <br/><b>Fecha | Autor  |  Cambios</b>
 * <ul>
 * <li>Mar 12, 2007 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Creaci&oacute;n del Objeto.</li>
 *
 * </ul>
 */

package com.psiqueware.alheli.dao.hibernate;


import org.hibernate.HibernateException;
import org.hibernate.type.NullableType;
import org.hibernate.type.TypeFactory;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

/**
 * Implements a generic enum user type identified / represented by a single identifier / column.
 * <p><ul>
 * <li>The enum type being represented by the certain user type must be set
 * by using the 'enumClass' property.</li>
 * <li>The identifier representing a enum value is retrieved by the identifierMethod.
 * The name of the identifier method can be specified by the
 * 'identifierMethod' property and by default the name() method is used.</li>
 * <li>The identifier type is automatically determined by
 * the return-type of the identifierMethod.</li>
 * <li>The valueOfMethod is the name of the static factory method returning
 * the enumeration object being represented by the given indentifier.
 * The valueOfMethod's name can be specified by setting the 'valueOfMethod'
 * property. The default valueOfMethod's name is 'valueOf'.</li>
 * </p>
 * <p/>
 * Example of an enum type represented by an int value:
 * {@link
 * public enum SimpleNumber {
 * Unknown(-1), Zero(0), One(1), Two(2), Three(3);
 * int value;
 * protected SimpleNumber(int value) {
 * this.value = value;
 * }
 * <p/>
 * public int toInt() { return value; }
 * public SimpleNumber fromInt(int value) {
 * switch(value) {
 * case 0: return Zero;
 * case 1: return One;
 * case 2: return Two;
 * case 3: return Three;
 * default:
 * return Unknown;
 * }
 * }
 * }
 * }
 * <p/>
 * The Mapping would look like this:
 * {@link
 * <typedef name="SimpleNumber" class="GenericEnumUserType">
 * <param name="enumClass">SimpleNumber</param>
 * <param name="identifierMethod">toInt</param>
 * <param name="valueOfMethod">fromInt</param>
 * </typedef>
 * <class ...>
 * ...
 * <property name="number" column="number" type="SimpleNumber"/>
 * </class>
 * }
 *
 * @author Martin Kersten
 * @since 05.05.2005
 */
public class GenericEnumUserType implements UserType, ParameterizedType
{

    private static final String DEFAULT_IDENTIFIER_METHOD_NAME = "name";

    private static final String DEFAULT_VALUE_OF_METHOD_NAME = "valueOf";

    private Class<? extends Enum> enumClass;

    private Class<?> identifierType;

    private Method identifierMethod;

    private Method valueOfMethod;

    private NullableType type;

    private int[] sqlTypes;

    public void setParameterValues(Properties parameters)
    {
        String enumClassName = parameters.getProperty("enumClass");
        try
        {
            enumClass = Class.forName(enumClassName).asSubclass(Enum.class);
        }
        catch (ClassNotFoundException cfne)
        {
            throw new HibernateException("Enum class not found", cfne);
        }

        String identifierMethodName = parameters.getProperty("identifierMethod", DEFAULT_IDENTIFIER_METHOD_NAME);

        try
        {
            identifierMethod = enumClass.getMethod(identifierMethodName, new Class[0]);
            identifierType = identifierMethod.getReturnType();
        }
        catch (Exception e)
        {
            throw new HibernateException("Failed to obtain identifier method", e);
        }

        type = (NullableType) TypeFactory.basic(identifierType.getName());

        if (type == null)
        {
            throw new HibernateException("Unsupported identifier type " + identifierType.getName());
        }

        sqlTypes = new int[]{type.sqlType()};

        String valueOfMethodName = parameters.getProperty("valueOfMethod", DEFAULT_VALUE_OF_METHOD_NAME);

        try
        {
            valueOfMethod = enumClass.getMethod(valueOfMethodName, new Class[]{identifierType});
        }
        catch (Exception e)
        {
            throw new HibernateException("Failed to obtain valueOf method", e);
        }
    }

    public Class returnedClass()
    {
        return enumClass;
    }

    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException
    {
        Object identifier = type.get(rs, names[0]);
        if (identifier == null)
        {
            return null;
        }

        try
        {
            return valueOfMethod.invoke(enumClass, new Object[]{identifier});
        }
        catch (Exception e)
        {
            throw new HibernateException("Exception while invoking valueOf method '" + valueOfMethod.getName() + "' of " +
                                         "enumeration class '" + enumClass + "'", e);
        }
    }

    public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException
    {
        try
        {
            if (value == null)
            {
                st.setNull(index, type.sqlType());
            }
            else
            {
                Object identifier = identifierMethod.invoke(value, new Object[0]);
                type.set(st, identifier, index);
            }
        }
        catch (Exception e)
        {
            throw new HibernateException("Exception while invoking identifierMethod '" + identifierMethod.getName() + "' of " +
                                         "enumeration class '" + enumClass + "'", e);
        }
    }

    public int[] sqlTypes()
    {
        return sqlTypes;
    }

    public Object assemble(Serializable cached, Object owner) throws HibernateException
    {
        return cached;
    }

    public Object deepCopy(Object value) throws HibernateException
    {
        return value;
    }

    public Serializable disassemble(Object value) throws HibernateException
    {
        return (Serializable) value;
    }

    public boolean equals(Object x, Object y) throws HibernateException
    {
        return x == y;
    }

    public int hashCode(Object x) throws HibernateException
    {
        return x.hashCode();
    }

    public boolean isMutable()
    {
        return false;
    }

    public Object replace(Object original, Object target, Object owner) throws HibernateException
    {
        return original;
    }
}